package org.quickserver.net.server;

import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.quickserver.net.ServerHook;
import org.quickserver.net.server.impl.BasicClientHandler;
import org.quickserver.net.server.impl.NonBlockingClientHandler;
import org.quickserver.util.Assertion;
import org.quickserver.util.MyString;
























public class GhostSocketReaper
  extends Thread
  implements ServerHook
{
  private static final Logger logger = Logger.getLogger(GhostSocketReaper.class.getName());
  
  private QuickServer quickserver;
  
  private volatile boolean stopFlag;
  
  private long timeOut = 0L;
  private long timeOutDelay = 0L;
  private ClientIdentifier clientIdentifier = null;

  
  public void initHook(QuickServer quickserver) {
    this.quickserver = quickserver;
    this.clientIdentifier = quickserver.getClientIdentifier();
  }
  
  public boolean handleEvent(int event) {
    if (event == 101) {
      
      setDaemon(true);
      this.stopFlag = false;
      setName("GhostSocketReaper-For-(" + this.quickserver + ")");
      start();
      return true;
    }  if (event == 202) {
      
      this.stopFlag = true;
      return true;
    } 
    return false;
  }
  
  public String info() {
    StringBuilder sb = new StringBuilder();
    sb.append("GhostSocketReaper - ServerHook");
    return sb.toString();
  }
  
  public void run() {
    logger.log(Level.FINE, "Starting GhostSocketReaper thread - {0}", this.quickserver.getName());
    if (this.quickserver.getTimeout() <= 0) {
      this.stopFlag = true;
      logger.log(Level.INFO, "Timeout is less than 0, so will exit - {0}", this.quickserver.getName());
      return;
    } 
    this.timeOut = this.quickserver.getTimeout();
    
    if (this.quickserver.getBasicConfig().getServerMode().getBlocking()) {
      this.timeOutDelay = 1000L;
    }
    List ghostList = new ArrayList();
    long searchSleepTime = this.timeOut / 2L;
    int failCount = 0;
    boolean flag = false;
    while (!this.stopFlag) {
      try {
        try {
          sleep(searchSleepTime);
        } catch (InterruptedException ie) {
          logger.log(Level.WARNING, "InterruptedException : {0}", ie.getMessage());
        } 
        
        if (failCount < 4)
        { flag = optimisticGhostSocketsFinder(ghostList);
          if (!flag) {
            failCount++;
          } else {
            failCount = 0;
          }  }
        else { syncGhostSocketsFinder(ghostList);
          failCount = 0; }
        
        cleanGhostSockets(ghostList, true);
      }
      catch (Exception e) {
        logger.fine("Exception : " + e);
        if (Assertion.isEnabled()) {
          logger.finest("StackTrace:\n" + MyString.getStackTrace(e));
        }
      } 
    } 

    
    while (this.stopFlag) {
      try {
        try {
          sleep(searchSleepTime);
        } catch (InterruptedException ie) {
          logger.warning("InterruptedException : " + ie.getMessage());
        } 
        
        if (!this.quickserver.isClosed()) {
          break;
        }
        
        if (this.quickserver.getClientCount() <= 0L) {
          try {
            Thread.yield();
            sleep(1000L);
          } catch (InterruptedException ie) {
            logger.warning("InterruptedException : " + ie.getMessage());
          } 
          this.quickserver.closeAllPools();
          break;
        } 
      } catch (Exception e) {
        logger.fine("Exception : " + e);
        if (Assertion.isEnabled()) {
          logger.finest("StackTrace:\n" + MyString.getStackTrace(e));
        }
        break;
      } 
    } 
    logger.log(Level.INFO, "Returning from GhostSocketReaper thread - {0}", this.quickserver.getName());
  }

  
  private long getCurrentTime() { return (new Date()).getTime(); }

  
  private boolean optimisticGhostSocketsFinder(List list) {
    Iterator<ClientHandler> iterator = null;
    ClientHandler clientHandler = null;
    int count = 0;
    long currentTime = getCurrentTime();
    try {
      iterator = this.clientIdentifier.findAllClient();





      
      while (iterator.hasNext()) {
        count++;
        if (count == 60) {
          currentTime = getCurrentTime();
          count = 1;
        } 
        clientHandler = iterator.next();
        
        checkClientHandlerForGhostSocket(clientHandler, currentTime, list);
        
        if (list.size() > 100) {
          logger.fine("Found about 100 ghost sockets, lets clean..");
          break;
        } 
      } 
    } catch (ConcurrentModificationException e) {
      
      return false;
    } 
    return true;
  }
  
  private void syncGhostSocketsFinder(List list) {
    Iterator<ClientHandler> iterator = null;
    ClientHandler clientHandler = null;
    int count = 0;
    long currentTime = getCurrentTime();
    synchronized (this.clientIdentifier.getObjectToSynchronize()) {
      iterator = this.clientIdentifier.findAllClient();
      if (iterator.hasNext()) {
        logger.finest("ENTER");
      }
      while (iterator.hasNext()) {
        count++;
        if (count == 60) {
          currentTime = getCurrentTime();
          count = 1;
        } 
        clientHandler = iterator.next();
        
        checkClientHandlerForGhostSocket(clientHandler, currentTime, list);
        
        if (list.size() > 100) {
          logger.fine("Found about 100 ghost sockets, lets clean..");
          break;
        } 
      } 
    } 
  }
  
  private void cleanGhostSockets(List<ClientHandler> list, boolean checkTimeout) {
    long currentTime = getCurrentTime();
    long commTime = 0L;
    long diff = -1L;
    ClientHandler clientHandler = null;
    
    int size = list.size();
    if (size > 0) {
      logger.log(Level.FINE, "Found ghost sockets: {0}", Integer.valueOf(size));
    } else {
      return;
    } 
    for (int i = 0; i < size; i++) {
      try {
        clientHandler = list.get(i);
        
        if (((BasicClientHandler)clientHandler).getWillClean()) {
          logger.log(Level.FINEST, "Not closing client {0}, WillClean is true", clientHandler);
          
          continue;
        } 
        if (checkTimeout) {
          this.timeOut = clientHandler.getTimeout() + this.timeOutDelay;
          if (clientHandler.getLastCommunicationTime() != null) {
            commTime = clientHandler.getLastCommunicationTime().getTime();
            diff = currentTime - commTime;
            
            if (diff < this.timeOut && !clientHandler.isClosed()) {
              logger.log(Level.FINEST, 
                  "Not closing client {0}, must have been reassigned. CommTime(sec): {1}, Diff(sec): {2}", 
                  new Object[] { clientHandler, Long.valueOf(commTime / 1000L), Long.valueOf(diff / 1000L) });
              
              continue;
            } 
          } 
        } 
        if (!clientHandler.isClosed())
        { logger.log(Level.FINEST, "Closing client {0}", clientHandler.getName());
          try {
            if (clientHandler.hasEvent(ClientEvent.RUN_BLOCKING)) {
              clientHandler.closeConnection();
            }
            else if (((NonBlockingClientHandler)clientHandler).getThreadAccessCount() != 0) {
              clientHandler.closeConnection();
              
              Object obj = clientHandler.getInputStream();
              if (obj != null) {
                synchronized (obj) {
                  clientHandler.getInputStream().notifyAll();
                } 
              }
            } else {
              clientHandler.addEvent(ClientEvent.CLOSE_CON);
              this.quickserver.getClientPool().addClient(clientHandler);
            }
          
          } catch (Exception er) {
            logger.log(Level.FINE, "Error closing client {0} : {1}", 
                new Object[] { clientHandler, er });
            clientHandler.forceClose();
          }
           }
        else if (!clientHandler.hasEvent(ClientEvent.RUN_BLOCKING))
        { logger.log(Level.FINEST, "Notifying IO of client {0}", clientHandler);
          Object obj = clientHandler.getInputStream();
          if (obj != null) {
            synchronized (obj) {
              clientHandler.getInputStream().notifyAll();
            } 
            
            logger.log(Level.FINEST, "Returning objs to pool {0}", clientHandler);
            if (this.quickserver.getClientDataPool() != null && clientHandler.getClientData() != null) {
              this.quickserver.getClientDataPool().returnObject(clientHandler.getClientData());
            }
            logger.log(Level.FINEST, "{0} returning {1}", 
                new Object[] { Thread.currentThread().getName(), getName() });
            this.quickserver.getClientHandlerPool().returnObject(clientHandler);
          }  }
        else { logger.log(Level.FINEST, 
              "Skipping closed {0} since in blocking mode.. this should clean up it self.", clientHandler); }
        
        continue;
      } catch (Exception ee) {
        logger.log(Level.FINE, "Exception forcing the close : {0}", ee);
        if (Assertion.isEnabled())
          logger.log(Level.FINEST, "StackTrace:\n{0}", MyString.getStackTrace(ee)); 
        continue;
      } 
    } 
    list.clear();
  }

  
  private void checkClientHandlerForGhostSocket(ClientHandler clientHandler, long currentTime, List<ClientHandler> list) {
    if (clientHandler == null) {
      return;
    }
    
    if (clientHandler.isClosed()) {
      logger.log(Level.FINEST, 
          "Not connected .. so returning ClientData and ClientHandler objects for : {0}", clientHandler);
      list.add(clientHandler);
      
      return;
    } 
    if (clientHandler.getTimeout() <= 0) {
      return;
    }

    
    if (clientHandler.getLastCommunicationTime() == null)
      return; 
    long commTime = clientHandler.getLastCommunicationTime().getTime();
    long diff = currentTime - commTime;
    
    long timeOutToUse = clientHandler.getTimeout() + this.timeOutDelay;
    if (diff >= timeOutToUse) {
      logger.log(Level.FINE, "Closable client {0}, Time diff(sec) : {1}", 
          new Object[] { clientHandler, Long.valueOf(diff / 1000L) });
      list.add(clientHandler);
    } 
  }
}
